﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows.Data;
using Caffeine.Core;
using Caffeine.Examples.Contacts.Repository;
using Caffeine.Wpf;
using Microsoft.Practices.Unity;

namespace Caffeine.Examples.Contacts.ViewModel
{
    public class ContactViewModel : ViewModelBase
    {
        #region State

        private readonly IUnityContainer _container;
        private readonly IContactRepository _repository;
        private readonly ActionCommand _save;
        private readonly ActionCommand _remove; 
        private readonly ActionCommand _undoChanges;
        private readonly ActionCommand _addAddress;
        private readonly ViewModelProperty<Boolean> _isSelected;
        private readonly ViewModelProperty<Boolean> _isHydrated;
        private readonly ViewModelProperty<String> _firstName;
        private readonly ViewModelProperty<String> _lastName;
        private readonly ViewModelCollection<AddressViewModel> _addresses;
        private readonly ListCollectionView _addedAddressViewModels;
        private static readonly Func<String, ViewModelProperty<String>, List<ValidationError>, Boolean> FirstNameValidationMethod;
        private static readonly Func<String, ViewModelProperty<String>, List<ValidationError>, Boolean> LastNameValidationMethod;
        private static readonly Func<String, ViewModelProperty<String>, List<ValidationError>, Boolean> FirstName_IgnoreCommitted_ValidationMethod;
        private static readonly Func<String, ViewModelProperty<String>, List<ValidationError>, Boolean> LastName_IgnoreCommitted_ValidationMethod;

        #endregion

        #region Constructors

        static ContactViewModel()
        {
            FirstNameValidationMethod = ValidationHelper.GetStringValidationMethod
            (
                false,
                true,
                2,
                20,
                String.Empty,
                "First name must be at least {0} characters or more",
                "First name cannot be more than {0} characters long",
                String.Empty
            );

            FirstName_IgnoreCommitted_ValidationMethod = ValidationHelper.GetStringValidationMethod
            (
                true,
                true,
                2,
                20,
                String.Empty,
                "First name must be at least {0} characters or more",
                "First name cannot be more than {0} characters long",
                String.Empty
            );

            LastNameValidationMethod = ValidationHelper.GetStringValidationMethod
            (
                false,
                true,
                2,
                20,
                String.Empty,
                "Last name must be at least {0} characters or more",
                "Last name cannot be more than {0} characters long",
                String.Empty
            );

            LastName_IgnoreCommitted_ValidationMethod = ValidationHelper.GetStringValidationMethod
            (
                true,
                true,
                2,
                20,
                String.Empty,
                "Last name must be at least {0} characters or more",
                "Last name cannot be more than {0} characters long",
                String.Empty
            );
        }

        public ContactViewModel(IUnityContainer container)
        {
            _container = container;
            _repository = container.Resolve<IContactRepository>();
            _isSelected = RegisterValueTypeProperty<Boolean>("IsSelected", null);
            _isHydrated = RegisterValueTypeProperty<Boolean>("IsHydrated", null);
            _firstName = RegisterStringProperty("FirstName");
            _lastName = RegisterStringProperty("LastName");
            _addresses = RegisterCollectionProperty<AddressViewModel>("Addresses");
            _addedAddressViewModels = new ListCollectionView(Addresses);
            AddedAddressViewModels.Filter = CheckIsAddedViewModel;
            Addresses.CollectionChanged += OnAddressesChanged;

            _save = new ActionCommand(OnSave);
            _undoChanges = new ActionCommand(OnUndoChanges);
            _remove = new ActionCommand(OnRemove);
            _addAddress = new ActionCommand(OnAddAddress);
        }

        #endregion

        #region Public

        public IUnityContainer Container
        {
            get
            {
                return _container;
            }
        }

        public IContactRepository Repository
        {
            get
            {
                return _repository;
            }
        }

        public ActionCommand Save
        {
            get
            {
                return _save;
            }
        }

        public ActionCommand Remove
        {
            get
            {
                return _remove;
            }
        }

        public ActionCommand UndoChanges
        {
            get
            {
                return _undoChanges;
            }
        }

        public ActionCommand AddAddress
        {
            get
            {
                return _addAddress;
            }
        }

        public ViewModelProperty<Boolean> IsSelected
        {
            get
            {
                return _isSelected;
            }
        }

        public ViewModelProperty<Boolean> IsHydrated
        {
            get
            {
                return _isHydrated;
            }
        }

        public ViewModelProperty<String> FirstName
        {
            get
            {
                return _firstName;
            }
        }

        public ViewModelProperty<String> LastName
        {
            get
            {
                return _lastName;
            }
        }

        public ViewModelCollection<AddressViewModel> Addresses
        {
            get
            {
                return _addresses;
            }
        }

        public ListCollectionView AddedAddressViewModels
        {
            get
            {
                return _addedAddressViewModels;
            }
        }

        public void SaveContact()
        {
            if (FirstName.IsDirty || LastName.IsDirty)
            {
                Repository.SaveContact(this);
                Commit();
            }

            if (Addresses.IsDirty || IsDirtyWithin)
            {
                foreach (AddressViewModel address in Addresses.GetCommittedViewModels())
                {
                    if (address.IsDirty)
                    {
                        Repository.SaveAddress(address);
                    }
                }

                foreach (AddressViewModel address in Addresses.GetAddedViewModels())
                {
                    Repository.SaveAddress(address);
                }

                foreach (AddressViewModel address in Addresses.GetRemovedViewModels())
                {
                    Repository.RemoveAddress(address);
                }

                this.CommitGraph();

                if (IsAddedToCollection)
                {
                    (Parent as ContactManagerViewModel).Contacts.Commit(this);
                }
            }
        }

        public void RemoveDependencies()
        {
            foreach (AddressViewModel address in Addresses.GetCommittedViewModels())
            {
                Repository.RemoveAddress(address);
            }

            foreach (AddressViewModel address in Addresses.GetRemovedViewModels())
            {
                Repository.RemoveAddress(address);
            }
        }

        #endregion

        #region Protected

        protected override void OnCollectionStatusChanged(String parentCollectionPropertyName)
        {
            base.OnCollectionStatusChanged(parentCollectionPropertyName);

            if (parentCollectionPropertyName == Addresses.Name)
            {
                foreach (AddressViewModel address in Addresses)
                {
                    address.ConfigureCommandVisibility();
                }
            }
        }

        protected override void OnIsCommittedToCollection(String parentCollectionPropertyName)
        {
            base.OnIsCommittedToCollection(parentCollectionPropertyName);
            ArrangeValidation(true);
        }

        protected override void OnIsAddedToCollection(String parentCollectionPropertyName)
        {
            base.OnIsAddedToCollection(parentCollectionPropertyName);
            ArrangeValidation(false);
        }

        public override void Commit()
        {
            base.Commit();
            AddedAddressViewModels.Refresh();
        }

        protected override void OnPropertyIsDirtyChanged(String propertyName)
        {
            base.OnPropertyIsDirtyChanged(propertyName);

            if (propertyName == Addresses.Name)
            {
                AddedAddressViewModels.Refresh();
            }
        }

        #endregion

        #region Private

        private void OnSave(Object commandParameter)
        {
            SaveContact();
        }

        private void OnUndoChanges(Object commandParameter)
        {
            Reset();
        }

        private void OnRemove(Object commandParameter)
        {
            (this.GetParent(typeof(ContactManagerViewModel)) as ContactManagerViewModel).RemoveContact(this);
        }

        private void OnAddAddress(Object commandParameter)
        {
            Addresses.Add(new AddressViewModel(Container));
        }

        private void ArrangeValidation(Boolean ignoreCommitted)
        {
            if (ignoreCommitted)
            {
                FirstName.ValidationMethod = FirstName_IgnoreCommitted_ValidationMethod;
                LastName.ValidationMethod = LastName_IgnoreCommitted_ValidationMethod;
            }
            else
            {
                FirstName.ValidationMethod = FirstNameValidationMethod;
                LastName.ValidationMethod = LastNameValidationMethod;
            }

            if (!ignoreCommitted)
            {
                FirstName.Validate();
                LastName.Validate();
            }
        }

        private void OnAddressesChanged(Object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
            {
                foreach (ViewModelBase viewModel in e.NewItems)
                {
                    if (viewModel.IsAddedToCollection)
                    {
                        AddedAddressViewModels.Refresh();
                    }
                }
            }

            if (e.OldItems != null)
            {
                foreach (ViewModelBase viewModel in e.OldItems)
                {
                    if (viewModel.IsAddedToCollection)
                    {
                        AddedAddressViewModels.Refresh();
                    }
                }
            }
        }

        private static Boolean CheckIsAddedViewModel(Object viewModel)
        {
            return (viewModel as ViewModelBase).IsAddedToCollection;
        }

        #endregion
    }
}
